﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace kunukn.RecommendationEngine.AreaRecommendation.Code.Engine
{
    public static class MathUtil
    {
        /// <summary>
        /// def CosineSimilarity(a,b):
        ///    return dot(a,b) / (norm(a) * norm(b))
        /// 
        /// http://en.wikipedia.org/wiki/Cosine_similarity
        /// http://stackoverflow.com/questions/1997750/cosine-similarity
        /// http://stackoverflow.com/questions/1746501/can-someone-give-an-example-of-cosine-similarity-in-very-simple-graphical-way
        ///         
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static double CosineSimilarity(int[] a, int[] b)
        {
            if (a.Length != b.Length)
            {
                throw new ApplicationException("CosineSimilarity len mismatch");
            }
                
            double divisor = (Norm(a) * Norm(b));
            if (divisor == 0)
            {
                return 0;
            }
                
            return Dot(a, b) / divisor;
        }

        /// <summary>
        /// http://en.wikipedia.org/wiki/Jaccard_index
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static double JaccardIndexSimilarity(int[] a, int[] b)
        {
            if (a.Length != b.Length)
            {
                throw new ApplicationException("JaccardIndexSimilarity len mismatch");
            }                

            double intersection = 0;
            double union = 0;

            int len = a.Length;

            // intersection
            for (int i = 0; i < len; i++)
            {
                var ai = a[i];
                var bi = b[i];
                                
                intersection += Math.Min(ai, bi);
            }

            // union
            for (int i = 0; i < len; i++)
            {
                var ai = a[i];
                var bi = b[i];

                union += ai + bi; // union += Math.Max(ai, bi);
            }

            if (union == 0)
            {
                return 0;
            }
                
            return intersection/union; 
        }

        /// <summary>
        /// def dot(a,b):
        /// n = length(a)
        /// sum = 0
        /// for i in xrange(n):
        ///    sum += a[i] * b[i];
        /// return sum
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        static double Dot(int[] a, int[] b)
        {
            int n = a.Length;
            double sum = 0;
            for (int i = 0; i < n; i++)
            {
                sum += a[i] * b[i];                
            }

            return sum;
        }

        /// <summary>               
        ///    def norm(a):
        ///      n = length(a)
        ///      for i in xrange(n):
        ///        sum += a[i] * a[i]
        ///       return math.sqrt(sum)        
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        static double Norm(IEnumerable<int> a)
        {
            double sum = a.Aggregate<int, double>(0, (current, t) => current + t*t);
            return Math.Sqrt(sum);
        }
    }
}